package edu.uoc.tfc.exuoc.controladors;


import edu.uoc.tfc.exuoc.config.Config;
import edu.uoc.tfc.exuoc.model.Sessio;
import edu.uoc.tfc.exuoc.model.Usuari;

import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.LinkedList;

import edu.uoc.tfc.exuoc.utils.Eines;
import edu.uoc.tfc.exuoc.utils.seguretat.Encriptar;
import edu.uoc.tfc.exuoc.errors.ExisteixException;
import edu.uoc.tfc.exuoc.errors.NoActiuException;


import org.apache.struts2.ServletActionContext;
import org.apache.struts2.dispatcher.SessionMap;


/**
 *
 * @author emili A. Moreno
 * El gestor només permet una sessio per login entrat
 */

//TODO:16-11-2011:Emili: Caldria sincronitzar l'acabament d'una sessio per temps
// i l'objecte guardat a la llista de sessions.
// Ens trobem que després d'un periode d'inactivitat la sessió caduda i l'usuari
// no es pot tornar a autenticar ja que el sistema diu que ja està logat.

// Cal fer un mètode ( com a mínim ) que faci neteja dels objectes de sessio
// que tenen associada una sessio (HTTP) caducada.

public class GestorSessions {
    private static List<Sessio> llista=new LinkedList<Sessio>();
    
    private GestorLogs log=GestorLogs.getLogger(this.getClass().getSimpleName());


    //TODO: 14-11-2011:Emili: Es pot aprofitar aquesta llista per fer
    //  un cas d'ús que ensenyi o compti els usuaris que estan logats
    //Accés exclussiu per altres gestors
    protected  List<Sessio> getLlista() {
        return GestorSessions.llista;
    }

    protected void setLlista(List<Sessio> llista) {
        GestorSessions.llista = llista;
    }

    public GestorLogs getLog() {
        return log;
    }

    public void setLog(GestorLogs log) {
        this.log = log;
    }

    /**
     *  Crea un id per la sessió
     * @param cadena
     * @return
     */
    private String crearIdSessio(String cadena){

        // Per valors aleatoris
        //java.util.Random random = new Random();
        //return  random.nextLong().toString;
        //
        String data=new Date().toString();
        Encriptar e = new Encriptar();

        //Es crea un id encriptat de cadena + la data
        return e.md5(cadena.concat(data));
    }

  

    /**
     *  Crea una nova sessio + variables de sessio HTTP. Cas de que l'usauri ja
     *  existeixi a la llista de sessions es comprova si aquesta ha caducat, cas
     *  afirmatiu es neteja i es crea una de nova
     * @param login
     * @param contrasenya
     * @return
     */
    //Mètode BO fent servir les entrades del Gestor d'usuaris
    public Sessio crear(String login, String contrasenya) throws ExisteixException,
            NoActiuException{
        Long id=Config.valorNull;
        Sessio sessio=null;
        //GestorUsuaris gu=new GestorUsuaris();
        GestorUsuaris guI=GestorUsuaris.getInstance();

        // Es comprova que el login donat no estigui ja logat i no estigui caducat
        //Si està caducat s'esborra de la llista de sessions
        Sessio s1=cercaLogin(login);
        if (!s1.getId().equals(Config.valorNullStr))

            //Es comprova si el retorn de netejaCaducades és un id de sessió
           if (! this.netejaCaducades(s1.getLogin()).equals(Config.valorNullStr)){
               //Ep tenim un login i no caducat !!!
               throw new ExisteixException("login repetit : "+login);
           }
        Usuari usuari=guI.autenticar(login,contrasenya);
        if (usuari!=null){

            //L'usuari és actiu continuem, cas contrari es produeix una excepció
            if (!usuari.getActiu())
                throw new NoActiuException("Usuari");
            //Es crea una sessio
            sessio=new Sessio(this.crearIdSessio(usuari.getLogin()),usuari.getLogin(),usuari.isAdmin(),usuari.isGestor(),true);
            //S'afegeix a la llista de sessions
            if (!this.getLlista().add(sessio)) sessio=null;
            else{
                try {

                    SessionMap s= new SessionMap(ServletActionContext.getRequest());
                    s.put("usuari", (String)ServletActionContext.getRequest().getParameter("login"));
                    s.put("isAdmin", usuari.isAdmin());
                    s.put("isGestor", usuari.isGestor());
                    s.put("isLogin", sessio.getIsLogin());
                    s.put("idSessio", sessio.getId());


/*                   Map sessioHTTP = ActionContext.getContext().getSession();
                    HttpServletRequest request = ServletActionContext.getRequest();

                    sessioHTTP.put("usuari", (String)request.getParameter("login"));
                    sessioHTTP.put("isAdmin", usuari.isAdmin());
                    sessioHTTP.put("isGestor", usuari.isGestor());
                    sessioHTTP.put("isLogin", sessio.getIsLogin());
                    sessioHTTP.put("idSessio", sessio.getId());
*/

                }catch (Exception e){
                    this.getLog().error("Error de creació de les variables de sesio. Login:"+login,e);
                }

            }

        }
        //this.printLlistat();
        guI.tancar();
        return sessio;
    }

    /**
     *  Mètode que posa la data de sessió a la data actual, quan es consulta una
     * pàgina de l'aplicatiu, d'aquesta forma s'evita que la sessió caduqui
     * encara que l'usuari estigui consultant pàgines.
     * Aquest mètode s'ha de posar a cada consulta jsp
     * @param idS
     * @return
     */
    public boolean actualitzarDataSessio(String idS){
        boolean correcte=true;
        try{
            Sessio s=this.cercar(idS);
            if (s!=null){
                //Actualitzem la data
                s.setDataInici(new Date());
                //Actualitzem la sessió a la llista de sessions
                this.getLlista().set( this.getLlista().indexOf(s),s);

            }

        }catch(Exception e){
            this.getLog().error("No 'sha actualitzat la data de sessió"+e.getLocalizedMessage());
            correcte=false;
        }

        return correcte;

    }

    /**
     *  mètode que indica si cert si la data indicada sobrepassa el temps de
     * caducitat indicat per LIMITSESSIO (expressat en milisegons)
     * @param dt1
     * @return true si la la diferència de la data actual i dt1 > LIMITSESSIO
     */

    private boolean caducat(Date dt1){
        boolean mort=false;
        Date dt2=new Date();

        if ( dt1==null ) dt1=new Date();
        try {
            //this.getLog().info("Diferència de dates : "+(dt2.getTime() - dt1.getTime()));
            if ( (dt2.getTime() - dt1.getTime() ) >Config.LIMITSESSIO*1000){
                mort=true;
            }
        }catch(NullPointerException e){
            this.getLog().info("Caducat: S'ha indicat una data amb valor null");
        }

        return mort;
    }
    /**
     *  mètode que s'engega cada vegada que es fa una autentificació.
     * la seva funció consisteix en eliminar aquelles sessions guardades que han
     * caducat, valor controlat per Config.LIMITSESSIO
     */
    private void netejaCaducades(){
        List <Sessio> llistaS=this.getLlista();
        Iterator<Sessio> it = (Iterator<Sessio>)llistaS.iterator();


        while (it.hasNext()){
            Sessio s=it.next();
            //Es comprova la data de la sessió
            try{
                this.getLog().debug("Limit: "+Config.LIMITSESSIO);
                this.getLog().debug("Usuari:"+s.getLogin());
                this.getLog().debug("   Actual:"+new java.util.Date()+":"+
                        new java.util.Date().getTime() );
                this.getLog().debug("   sessio:"+s.getDataInici()+":"+
                        s.getDataInici().getTime());
                this.getLog().debug("   Resta:"+
                        ( (new java.util.Date() ).getTime() - s.getDataInici().getTime()));
                if ( this.caducat(s.getDataInici()))
                    //llistaS.remove(s);
                    this.eliminar(s.getId());

            }catch(Exception e){
                this.getLog().error("NetejaCaducades: Error inexperat: "+e.getLocalizedMessage());
            }

        }
    }
    /** 
     * Es busca un login d'usuari a la llista de sessions per comprovar si ha
     * caducat, cas de que sigui aixi l'eliminem perquè l'usuari pugui tornar a 
     * entrar
     * @param login
     * @return id de sessió de l'usuari que NO ha caducat Config.ValorNUll si no
     * s'ha trobat o la sessió ha caducat i s'ha netejada.
     */
    private String netejaCaducades(String login){
        String idS=Config.valorNullStr;
        try {
            Sessio s=this.cercaLogin(login);
            if (!s.getId().equals(Config.valorNullStr)){
                if (this.caducat(s.getDataInici())) this.eliminar(s.getId());
                else idS=s.getId();
            }
        }catch(NullPointerException ex){
            this.getLog().error("netejaCaducades:S'ha processat un valor null:"+ex.getLocalizedMessage());
        }
        return idS;
    }

  
    /**
     *  Eliminar una sessió
     * @param idSessio
     * @return True, si s'ha realitzat el procés correctament.
     */
    public Boolean eliminar(String idSessio){

        Boolean correcte=true;
        Sessio sessio=this.cercar(idSessio);
      
        //Es comprova si tenim les dades de sessió a la llista de sessions
        if (sessio!=null){

            //Treguem la sessió de la llista
            if (!this.getLlista().remove(sessio)){
                    this.log.error("Error d'eliminació de la sessio. id: "+idSessio);
                    correcte=false;
            }
        }else {
            this.log.error("No s'ha trobat la sessio id: {}", idSessio);
            correcte=false;
        }
        /* Encara que no s'hagi trobat la sessió a la llista potser tenim les
         * dades de HTTP_SESSION actives i per tant s'han d'anul·lar. Aquest és
         * un cas típic de quan es para el servidor d'aplicacions web i encara
         * hi ha usuaris connectats
         *
         */
        try {
            SessionMap s= new SessionMap(ServletActionContext.getRequest());
            s.invalidate();
            

        }catch (Exception e){
            this.log.error("Error d'eliminació de variables de sessió HTTP",
                        e.getLocalizedMessage());
            correcte=false;
        }
        //this.printLlistat();
        return correcte;

    }

    /**
     *  Es busca una sessio mitjançant l'id de sessió
     * @param idS
     * @return Sessió trobada o NULL
     */
    public Sessio cercar(String idS){

        Sessio s=new Sessio(idS);
        return this.cercar(s);
    }

    /**
     * Cerca d'una sessió a la llista de sessions
     * @param sessio
     * @return la sessio trobada o NULL
     */
    public Sessio cercar(Sessio sessio){

        if (this.getLlista().contains(sessio))
            sessio=this.getLlista().get(GestorSessions.llista.indexOf(sessio));
        else sessio=null;
        return sessio;
    }

    /**
     * Es busca un login d'usuari a la llista de sessions
     * @param l
     * @return Retorna una sessió nova si no s'ha trobat, cas contrari retorna
     * la sessió del login indicat
     */
    public Sessio cercaLogin(String l){
        Boolean trobat=false;
        Sessio s=new Sessio();

        this.getLog().info("Cerca per login: "+l);
        s.setId(Config.valorNullStr);
        Iterator<Sessio> it=this.llistat();

        while (it.hasNext() && !trobat){
            Sessio s1 =(Sessio)it.next();
            String login=s1.getLogin().trim();

            if (login.equals(l.trim()) )
                s=s1;
                //trobat=true;
        }
        //return trobat;
        return s;
    }

    /**
     *
     * @return   llista de sessions actives
     */
    private Iterator<Sessio> llistat(){
        return this.getLlista().iterator();
    }

    private void printLlistat(){
        Iterator<Sessio> it=this.llistat();
        
        Eines.escriu("-------llistat----------");
        while (it.hasNext()){
            Eines.escriu(it.next().toString());
        }
    }



   /** ************************************
    * TEST
    */


     private void testCrearIdSessio(GestorSessions gs){

        Eines.escriu("id creat:"+gs.crearIdSessio("emili"));
     }

     private void testNova(GestorSessions gs){

        try {
            Eines.escriu(gs.crear("montse", "123456").getLogin());
            Eines.escriu(gs.crear("emili", "123456").getLogin());
            Eines.escriu(gs.crear("maripili", "123456").getLogin());
            Eines.escriu(gs.crear("maripili", "123456").getLogin());

        } catch (ExisteixException ex) {
            this.getLog().error("Usuari ja logat. "+ex.getNomParam());
        } catch (NullPointerException ex) {
            this.getLog().error("No s'ha trobat cap login :"+ex.getLocalizedMessage());
        }catch (NoActiuException ex) {
            this.getLog().error("Usuari no actiu :"+ex.getLocalizedMessage());
        }
     }

     private void testCercar(GestorSessions gs){
         Sessio s1=null;
       try {
            s1 = gs.crear("emili", "123456");
        } catch (ExisteixException ex) {
            this.getLog().error(ex.getLocalizedMessage());
        } catch (NoActiuException ex) {
            this.getLog().error(ex.getLocalizedMessage());
        }

         Eines.escriu("Sessio nova, Login:"+s1.getLogin());
         Eines.escriu("admin: "+gs.cercar(s1).getIsAdmin());
         Eines.escriu("gestor: "+gs.cercar(s1).getIsGestor());
         Sessio s=gs.cercar(s1);
         Eines.escriu("Id cercat: "+s);
         Eines.escriu("Cerca per id, ID:"+gs.cercar(s));
         Eines.escriu("trobat login :"+gs.cercaLogin("emili"));
     }

     private void testCicle(GestorSessions gs){
         Sessio s=null, s1 =null ,s2 = null;
         Iterator<Sessio> it=null;

         Eines.escriu("llista inicial");
         it=gs.getLlista().iterator();
         while (it.hasNext()){
             s=it.next();
             Eines.escriu(s.toString());
         }
        try {
            s1 = gs.crear("emili", "123456");
            s2=gs.crear("montse", "123456");
        } catch (ExisteixException ex) {
            this.getLog().error(ex.getLocalizedMessage());
        } catch (NoActiuException ex) {
            this.getLog().error(ex.getLocalizedMessage());
        }


         Eines.escriu("llista amb dos sessions mes");
         it=gs.getLlista().iterator();
         while (it.hasNext()){
             s=it.next();
             Eines.escriu(s.toString());
         }

         Eines.escriu("Data de la sessio per "+s.getLogin()+": "+s.getDataInici());
         Eines.escriu("Esperem una mica....");
         int i=0;
         while (i < 12){
            try {
                Thread.sleep(1000L);
                System.out.print(".");
            } catch (InterruptedException ex) {
                this.getLog().info("Ha arribat una interrupció: "+ex.getLocalizedMessage());
            }

            i++;
         }
         Eines.escriu();
         Eines.escriu("La sessió és caducada ? "+this.caducat(s.getDataInici()));
         Eines.escriu();
         Eines.escriu("Actualitzem la seva data");
         this.actualitzarDataSessio(s.getId());
         Eines.escriu("Data de la sessio per "+s.getLogin()+": "+s.getDataInici());







         Eines.escriu("Eliminant Primera sessio");
         gs.eliminar(s1.getId());
         Eines.escriu("Eliminant segona sessio");
         gs.eliminar(s2.getId());
         Eines.escriu("Provem de tornar a elimnar la segona sessio");
         gs.eliminar(s2.getId());
         
         Eines.escriu("llista buida");
         it=gs.getLlista().iterator();
         while (it.hasNext()){
             s=it.next();
             Eines.escriu(s.toString());
         }
     }
     
     private void testEliminar(GestorSessions gs ){
         Sessio s= new Sessio(), s1=null;
         String id="e7e0f29172d863ebdda7c12309183170";
         s.setId(id);
         Eines.escriu("id:"+s);
        try {
            s1=gs.crear("emili", "123456");
        } catch (ExisteixException ex) {
               this.getLog().error(ex.getLocalizedMessage());
            //this.getLog().getLogger(this.getClass().getName()).log(org.apache.log4j.Level.INFO, ex);
        } catch (NoActiuException ex) {
            this.getLog().error(ex.getLocalizedMessage());
        }
         gs.eliminar(id);
     }
   /**************************************
    * MAIN
    */


   public static void main(String args[]){

       GestorSessions gs = new GestorSessions();

       //gs.testCrearIdSessio(gs);
       //gs.testNova(gs);
       //gs.netejaCaducades();
       //gs.testCercar(gs);
       gs.testCicle(gs);
       //gs.testEliminar(gs);



    }

}
